///  <copyright file="MallowMain.cs" company="Infozoom" author="Stefan Zschocke">
///    You must not remove this notice, or any other, from this software.
///    Otherwise do with it whatever you want.
///    infoZoom may not be made liable for any damages whatsoever 
///    (including, without limitation, damages for loss of business profits, 
///    business interruption, loss of business information or other pecuniary loss)
///    arising out of use of or inability to use the Software
/// </copyright>
using System;
using System.Collections;
using System.Xml;


namespace Mallow
{
    /// <summary>
    /// Todo: add comments
    /// </summary>
    public class Mallow : IDisposable
    {
        public enum EErrorNo 
        { 
            None = 0, 
            InvalidCmdLine, 
            MultipleDirectoryNodes, 
            DirectoryNodeNotFound, 
            WixNsNotFound, 
            InvalidXml 
        };
        public static readonly string[] ErrorStrings = {
            "No Error"
            ,"InvalidCmdLine"
            ,"Multiple Directory or DirectoryRef Nodes with the Id {0}"
            ,"No Directory or DirectoryRef Nodes with the Id {0} found"
            ,"Missing xmlns Attribute in the Input file"
            ,"Invalid XML File {0}"
        };

        private IComparer mFileNameComparer;
        private System.Xml.XmlDocument mDoc;
        private readonly string mWixPrefix = "Wix";
        private readonly string mWixUri = "http://schemas.microsoft.com/wix/2006/wi";
        private NameGenerator mComponentNameGenerator, mDirectoryNameGenerator, mFileNameGenerator;
        private XmlNamespaceManager mWixNamespaceManager;
        private bool mNewFile = false;
        bool mGenGuids, mMultiFilesPerComponent, mSemanticIds;

        public class Exception : System.Exception
        {
            public Exception(EErrorNo errNo, params string[] arguments) : base(string.Format(ErrorStrings[(int)errNo], arguments))
            {
                mErrNo = errNo;
            }
            public EErrorNo ErrNo { get { return mErrNo; } }
            EErrorNo mErrNo;
        }
        
        public Mallow(bool oneFilePerComponent, bool autoGenGuids, bool generateSemanticIds)
        {
            mDoc = null;
            mFileNameComparer = null;
            mMultiFilesPerComponent = !oneFilePerComponent;
            mSemanticIds = generateSemanticIds;
            this.mGenGuids = autoGenGuids;
       }

        public void Dispose() { }
        
        public void NewDoc(string directoryRef)
        {
            string s =
               "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + Environment.NewLine +
               "<Wix xmlns=\"" + mWixUri + "\">" + Environment.NewLine +
               "  <Fragment>" + Environment.NewLine +
               "    <DirectoryRef Id=\"" + directoryRef + "\">" + Environment.NewLine +
               "    </DirectoryRef>" + Environment.NewLine +
               "  </Fragment>" + Environment.NewLine +
               "</Wix>" + Environment.NewLine;

            mDoc = new System.Xml.XmlDocument();
            mDoc.Load(new System.IO.StringReader(s));
            mNewFile = true;
            InitCommon();
        }

        public void OpenDoc(string inputFile)
        {
            mDoc = new System.Xml.XmlDocument();
            try
            {
                mDoc.Load(inputFile);
            }
            catch
            {
                throw new Exception(EErrorNo.InvalidXml, inputFile);
            }
            mNewFile = false;
            InitCommon();
        }

        private void InitCommon()
        {
            mFileNameComparer = new CaseInsensitiveComparer();
            if (mFileNameComparer.Compare(mDoc.DocumentElement.NamespaceURI, mWixUri) != 0)
            {
                throw new Exception(EErrorNo.WixNsNotFound);
            }
            mWixNamespaceManager = new XmlNamespaceManager(mDoc.NameTable);
            mWixNamespaceManager.AddNamespace(mWixPrefix, mWixUri);
            mComponentNameGenerator = new NameGenerator(mDoc.DocumentElement, mWixNamespaceManager, mWixPrefix + ":Component", "component", mSemanticIds);
            mDirectoryNameGenerator = new NameGenerator(mDoc.DocumentElement, mWixNamespaceManager, mWixPrefix + ":Directory", "directory", mSemanticIds);
            mFileNameGenerator = new NameGenerator(mDoc.DocumentElement, mWixNamespaceManager, mWixPrefix + ":File", "file", mSemanticIds);
        }
        
        public void SaveDoc(string outputFile)
        {
            mDoc.Save(outputFile);
        }

        public int Synch(string directoryNodeId, string directoryPath, string dirAlias)
        {
            XmlNode rootNode = FindDirectoryNode(directoryNodeId);
            if (rootNode == null)
            {
                throw new Exception(EErrorNo.DirectoryNodeNotFound, directoryNodeId);
            }
            
            RootDirectory dir = new RootDirectory(this, rootNode, false);
            ProtocolBuilder protocol = new ProtocolBuilder();
            dir.StartSynch(directoryPath, protocol, dirAlias);
            XmlNode comment = mDoc.CreateComment(protocol.GetProtocol(mNewFile));
            mDoc.DocumentElement.InsertBefore(comment, mDoc.DocumentElement.ChildNodes[0]);
            
            if (protocol.ComponentChangesDetected)
            {
                return 1;
            }
            return 0;
        }

        public bool ConfigMultiFilesPerComponent { get { return this.mMultiFilesPerComponent; } }

        public bool ConfigAutoGuid { get { return this.mGenGuids; } }

        internal IComparer FileNameComparer { get { return mFileNameComparer; } }
        internal XmlDocument Document { get { return mDoc; } }

        internal XmlNode CreateComponentNode(Directory dir, string fileName)
        {
            XmlNode node = Document.CreateElement("Component", mWixUri);
            XmlAttribute a = Document.CreateAttribute("Id");
            string id;
            if (mSemanticIds)
            {
                string[] n = dir.GetRelativeNames();
                if (!ConfigMultiFilesPerComponent)
                {
                    string[] nn = new string[n.Length + 1];
                    for (int i = 0; i < n.Length; i++) nn[i] = n[i];
                    nn[n.Length] = fileName;
                    n = nn;
                }
                id = mComponentNameGenerator.MakeCompositeName(n);
            }
            else
            {
                id = mComponentNameGenerator.GetNextFreeName();
            }
            a.Value = id;
            node.Attributes.Append(a);
            if (ConfigAutoGuid)
            {
                a = Document.CreateAttribute("Guid");
                a.Value = System.Guid.NewGuid().ToString().ToUpper();
                node.Attributes.Append(a);
            }
            a = Document.CreateAttribute("DiskId");
            a.Value = "1";
            node.Attributes.Append(a);
            return node;
        }

        internal XmlNode CreateFileNode(Directory dir, System.IO.FileInfo fInfo, bool setKeypath, string source)
        {
            XmlNode node = Document.CreateElement("File", mWixUri);
            XmlAttribute a = Document.CreateAttribute("Id");
            string id;

            if (mSemanticIds)
            {
                string[] n = dir.GetRelativeNames();
                string[] nn = new string[n.Length + 1];
                for (int i = 0; i < n.Length; i++) nn[i] = n[i];
                nn[n.Length] = fInfo.Name;
                id = mFileNameGenerator.MakeCompositeName(nn);
            }
            else
            {
                id = mFileNameGenerator.GetNextFreeName();
            }
            a.Value = id;
            node.Attributes.Append(a);
            string fName = fInfo.Name;

            a = Document.CreateAttribute("Name");
            a.Value = fName;
            node.Attributes.Append(a);

            a = Document.CreateAttribute("Source");
            a.Value = source == null ? fInfo.FullName : source;
            node.Attributes.Append(a);
            if (setKeypath)
            {
                a = Document.CreateAttribute("KeyPath");
                a.Value = "yes";
                node.Attributes.Append(a);
            }
            return node;
        }

        internal XmlNode CreateDirectoryNode(Directory dir, System.IO.DirectoryInfo fInfo)
        {
            XmlNode node = Document.CreateElement("Directory", mWixUri);
            XmlAttribute a = Document.CreateAttribute("Id");
            string id;

            if (mSemanticIds)
            {
                string[] n = dir.GetRelativeNames();
                string[] nn = new string[n.Length + 1];
                for (int i = 0; i < n.Length; i++) nn[i] = n[i];
                nn[n.Length] = fInfo.Name;
                id = mDirectoryNameGenerator.MakeCompositeName(nn);
            }
            else
            {
                id = mDirectoryNameGenerator.GetNextFreeName();
            }

            a.Value = id;
            node.Attributes.Append(a);
            string fName = fInfo.Name;

            a = Document.CreateAttribute("Name");
            a.Value = fName;
            node.Attributes.Append(a);

            return node;
        }

        private XmlNode FindDirectoryNode(string id)
        {
            XmlNodeList nodeList = mDoc.SelectNodes("descendant::" + mWixPrefix + ":Directory[attribute::Id=\"" + id + "\"] | descendant::" + mWixPrefix + ":DirectoryRef[attribute::Id=\"" + id + "\"]", mWixNamespaceManager);
            
            if (nodeList.Count > 1)
            {
                throw new Exception(EErrorNo.MultipleDirectoryNodes, id);
            }

            return nodeList.Count == 1 ? nodeList[0] : null;
        }

        static void RecursiveDump(XmlNode node)
        {
            System.Console.WriteLine(node.Name);
            foreach (XmlNode childNode in node.ChildNodes)
            {
                RecursiveDump(childNode);
            }
        }
    }
}
